/*
 * Copyright (C) 2013 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package im.yangqiang.android.ui.widget;

import android.content.Context;
import android.content.res.TypedArray;
import android.support.v4.view.NestedScrollingChild;
import android.support.v4.view.NestedScrollingChildHelper;
import android.support.v4.view.NestedScrollingParent;
import android.support.v4.view.NestedScrollingParentHelper;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.Animation.AnimationListener;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Transformation;
import android.widget.AbsListView;
import android.widget.ImageView;

import java.util.Arrays;


/**
 * 下拉刷新布局
 */
public class RefreshLayout extends ViewGroup implements NestedScrollingParent
{
    private static final String TAG = RefreshLayout.class.getSimpleName();
    private static final float DECELERATE_INTERPOLATION_FACTOR = 2f;
    private static final int ANIMATE_TO_TRIGGER_DURATION = 200;
    private static final int ANIMATE_TO_START_DURATION = 200;
    /**
     * 默认下拉刷新的最大位置高度
     */
    private static final int DEFAULT_REFRESH_HEIGHT = 80;
    private static final int[] LAYOUT_ATTRS = new int[]{
            android.R.attr.enabled
    };

    private final NestedScrollingParentHelper mNestedScrollingParentHelper;
    private final NestedScrollingChildHelper mNestedScrollingChildHelper;
    private final int[] mParentScrollConsumed = new int[2];
    private final int[] mParentOffsetInWindow = new int[2];
    private final DecelerateInterpolator mDecelerateInterpolator;
    protected int mFrom;
    protected int mOriginalOffsetTop;
    private View mTarget; // the target of the gesture
    private OnRefreshListener mListener;
    private boolean mRefreshing = false;
    private float mTotalUnconsumed;
    private int mCurrentTargetOffsetTop;
    private final Animation mAnimateToStartPosition = new Animation()
    {
        @Override
        public void applyTransformation(float interpolatedTime, Transformation t)
        {
            moveToStart(interpolatedTime);
        }
    };
    private boolean mOriginalOffsetCalculated = false;
    private ImageView mRefreshView;
    private int mRefreshViewIndex = -1;
    private RefreshGearDrawable mProgress;
    private float mTotalDragDistance;
    private final Animation mAnimateToCorrectPosition = new Animation()
    {
        @Override
        public void applyTransformation(float interpolatedTime, Transformation t)
        {
            int targetTop = 0;
            int endTarget = 0;
            endTarget = (int) (mTotalDragDistance - Math.abs(mOriginalOffsetTop));
            targetTop = (mFrom + (int) ((endTarget - mFrom) * interpolatedTime));
            int offset = targetTop - mTarget.getTop();
            setTargetOffsetTopAndBottom(offset, false);
        }
    };
    private boolean mNotify;
    private final AnimationListener mRefreshListener = new AnimationListener()
    {
        @Override
        public void onAnimationStart(Animation animation)
        {
        }

        @Override
        public void onAnimationRepeat(Animation animation)
        {
        }

        @Override
        public void onAnimationEnd(Animation animation)
        {
            if (mRefreshing)
            {
                if (mNotify)
                {
                    if (mListener != null)
                    {
                        mProgress.start();
                        mListener.onRefresh();
                    }
                }
            }
            else
            {
                mProgress.stop();
                setTargetOffsetTopAndBottom(mOriginalOffsetTop - mCurrentTargetOffsetTop, true /* requires update */);
                mCurrentTargetOffsetTop = mTarget.getTop();
            }

        }
    };
    private int mRefreshWidth;
    private int mRefreshHeight;

    /**
     * Simple constructor to use when creating a SwipeRefreshLayout from code.
     *
     * @param context
     */
    public RefreshLayout(Context context)
    {
        this(context, null);
    }

    /**
     * Constructor that is called when inflating SwipeRefreshLayout from XML.
     *
     * @param context
     * @param attrs
     */
    public RefreshLayout(Context context, AttributeSet attrs)
    {
        super(context, attrs);
        setWillNotDraw(false);
        mDecelerateInterpolator = new DecelerateInterpolator(DECELERATE_INTERPOLATION_FACTOR);

        final TypedArray a = context.obtainStyledAttributes(attrs, LAYOUT_ATTRS);
        setEnabled(a.getBoolean(0, true));
        a.recycle();

        final DisplayMetrics metrics = getResources().getDisplayMetrics();

        // 下拉刷新移动的最终偏移量
        mTotalDragDistance = DEFAULT_REFRESH_HEIGHT * metrics.density;

        mRefreshWidth = metrics.widthPixels;
        mRefreshHeight = (int) (mTotalDragDistance * metrics.density);

        createProgressView();
        ViewCompat.setChildrenDrawingOrderEnabled(this, true);

        mNestedScrollingParentHelper = new NestedScrollingParentHelper(this);

        mNestedScrollingChildHelper = new NestedScrollingChildHelper(this);
        setNestedScrollingEnabled(true);
    }

    @Override
    protected int getChildDrawingOrder(int childCount, int i)
    {
        if (mRefreshViewIndex < 0)
        {
            return i;
        }
        else if (i == childCount - 1)
        {
            return mRefreshViewIndex;
        }
        else if (i >= mRefreshViewIndex)
        {
            return i + 1;
        }
        else
        {
            return i;
        }
    }

    private void createProgressView()
    {
        mRefreshView = new ImageView(getContext());
        mProgress = new RefreshGearDrawable(getContext(), this);
        mProgress.setMaxHeight(mTotalDragDistance);
        mRefreshView.setImageDrawable(mProgress);
        addView(mRefreshView);
    }

    /**
     * Set the listener to be notified when a refresh is triggered via the swipe
     * gesture.
     */
    public void setOnRefreshListener(OnRefreshListener listener)
    {
        mListener = listener;
    }


    private void setRefreshing(boolean refreshing, final boolean notify)
    {
        if (mRefreshing != refreshing)
        {
            mNotify = notify;
            ensureTarget();
            mRefreshing = refreshing;
            if (mRefreshing)
            {
                animateOffsetToCorrectPosition(mCurrentTargetOffsetTop, mRefreshListener);
            }
            else
            {
                mProgress.end(new AnimationListener()
                {
                    @Override
                    public void onAnimationStart(Animation animation)
                    {

                    }

                    @Override
                    public void onAnimationEnd(Animation animation)
                    {
                        animateOffsetToStartPosition(mTarget.getTop(), mRefreshListener);
                    }

                    @Override
                    public void onAnimationRepeat(Animation animation)
                    {

                    }
                });
            }
        }
    }

    /**
     * @return Whether the SwipeRefreshWidget is actively showing refresh
     * progress.
     */
    public boolean isRefreshing()
    {
        return mRefreshing;
    }

    /**
     * Notify the widget that refresh state has changed. Do not call this when
     * refresh is triggered by a swipe gesture.
     *
     * @param refreshing Whether or not the view should show refresh progress.
     */
    public void setRefreshing(boolean refreshing)
    {
        if (refreshing && !mRefreshing)
        {
            mRefreshing = true;
            int endTarget = 0;
            endTarget = (int) (mTotalDragDistance + mOriginalOffsetTop);
            setTargetOffsetTopAndBottom(endTarget - mCurrentTargetOffsetTop, true /* requires update */);
            mNotify = false;
        }
        else
        {
            setRefreshing(refreshing, false /* notify */);
        }
    }

    private void ensureTarget()
    {
        if (mTarget == null)
        {
            for (int i = 0; i < getChildCount(); i++)
            {
                View child = getChildAt(i);
                if (!child.equals(mRefreshView))
                {
                    mTarget = child;
                    break;
                }
            }
        }
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom)
    {
        final int width = getMeasuredWidth();
        final int height = getMeasuredHeight();
        if (getChildCount() == 0)
        {
            return;
        }
        if (mTarget == null)
        {
            ensureTarget();
            return;
        }
        final View child = mTarget;
        final int childLeft = getPaddingLeft();
        final int childTop = getPaddingTop();
        final int childWidth = width - getPaddingLeft() - getPaddingRight();
        final int childHeight = height - getPaddingTop() - getPaddingBottom();
        child.layout(childLeft, mCurrentTargetOffsetTop, childLeft + childWidth, mCurrentTargetOffsetTop + childHeight);
        mRefreshView.layout((width / 2 - mRefreshWidth / 2), 0, (width / 2 + mRefreshWidth / 2), mRefreshHeight);
    }

    @Override
    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if (mTarget == null)
        {
            ensureTarget();
            return;
        }
        mTarget.measure(MeasureSpec.makeMeasureSpec(
                getMeasuredWidth() - getPaddingLeft() - getPaddingRight(),
                MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(
                getMeasuredHeight() - getPaddingTop() - getPaddingBottom(), MeasureSpec.EXACTLY));
        mRefreshView.measure(MeasureSpec.makeMeasureSpec(mRefreshWidth, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(mRefreshHeight, MeasureSpec.EXACTLY));
        if (!mOriginalOffsetCalculated)
        {
            mOriginalOffsetCalculated = true;
            mCurrentTargetOffsetTop = mOriginalOffsetTop = 0;
        }
        mRefreshViewIndex = -1;
    }

    @Override
    public void requestDisallowInterceptTouchEvent(boolean b)
    {
        if ((android.os.Build.VERSION.SDK_INT < 21 && mTarget instanceof AbsListView)
                || (mTarget != null && !ViewCompat.isNestedScrollingEnabled(mTarget)))
        {
            // Nope.
        }
        else
        {
            super.requestDisallowInterceptTouchEvent(b);
        }
    }

    @Override
    public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes)
    {
//        onNestedScrollAccepted(child, target, nestedScrollAxes);
        return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;
//        return isEnabled() && !mRefreshing && (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;
    }

    @Override
    public void onNestedScrollAccepted(View child, View target, int axes)
    {
        mNestedScrollingParentHelper.onNestedScrollAccepted(child, target, axes);
        startNestedScroll(axes & ViewCompat.SCROLL_AXIS_VERTICAL);
    }

    @Override
    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed)
    {
        Log.d("onNestedPreScroll", "dx:" + dx + "  dy:" + dy + "   consumed:" + Arrays.toString(consumed));
        if (dy > 0 && mTotalUnconsumed > 0)
        {
            if (dy > mTotalUnconsumed)
            {
                consumed[1] = dy - (int) mTotalUnconsumed;
                mTotalUnconsumed = 0;
            }
            else
            {
                mTotalUnconsumed -= dy;
                consumed[1] = dy;

            }
            moveSpinner(mTotalUnconsumed);
        }

//        final int[] parentConsumed = mParentScrollConsumed;
//        if (dispatchNestedPreScroll(dx - consumed[0], dy - consumed[1], parentConsumed, null))
//        {
//            consumed[0] += parentConsumed[0];
//            consumed[1] += parentConsumed[1];
//        }
    }

    @Override
    public int getNestedScrollAxes()
    {
        return mNestedScrollingParentHelper.getNestedScrollAxes();
    }

    @Override
    public void onStopNestedScroll(View target)
    {
        mNestedScrollingParentHelper.onStopNestedScroll(target);
        if (mTotalUnconsumed > 0)
        {
//            finishSpinner(mTotalUnconsumed);
//            mTotalUnconsumed = 0;
        }
        stopNestedScroll();
    }

    @Override
    public void onNestedScroll(final View target, final int dxConsumed, final int dyConsumed, final int dxUnconsumed, final int dyUnconsumed)
    {
//        dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, mParentOffsetInWindow);
        final int dy = dyUnconsumed + mParentOffsetInWindow[1];
        if (dy < 0)
        {
            mTotalUnconsumed += Math.abs(dy);
            moveSpinner(mTotalUnconsumed);
        }
        else
        {
            mTotalUnconsumed -= Math.abs(dy);
            moveSpinner(mTotalUnconsumed);
        }
    }

    @Override
    public boolean isNestedScrollingEnabled()
    {
        return mNestedScrollingChildHelper.isNestedScrollingEnabled();
    }

    @Override
    public void setNestedScrollingEnabled(boolean enabled)
    {
        mNestedScrollingChildHelper.setNestedScrollingEnabled(enabled);
    }

    @Override
    public boolean startNestedScroll(int axes)
    {
        return mNestedScrollingChildHelper.startNestedScroll(axes);
    }

    @Override
    public void stopNestedScroll()
    {
        mNestedScrollingChildHelper.stopNestedScroll();
    }

    @Override
    public boolean hasNestedScrollingParent()
    {
        return mNestedScrollingChildHelper.hasNestedScrollingParent();
    }

    @Override
    public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed,
                                        int dyUnconsumed, int[] offsetInWindow)
    {
        return mNestedScrollingChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed,
                dxUnconsumed, dyUnconsumed, offsetInWindow);
    }

    @Override
    public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow)
    {
        return mNestedScrollingChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);
    }

    @Override
    public boolean onNestedPreFling(View target, float velocityX,
                                    float velocityY)
    {
        return dispatchNestedPreFling(velocityX, velocityY);
    }

    @Override
    public boolean onNestedFling(View target, float velocityX, float velocityY,
                                 boolean consumed)
    {
        return dispatchNestedFling(velocityX, velocityY, consumed);
    }

    @Override
    public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed)
    {
        return mNestedScrollingChildHelper.dispatchNestedFling(velocityX, velocityY, consumed);
    }

    @Override
    public boolean dispatchNestedPreFling(float velocityX, float velocityY)
    {
        return mNestedScrollingChildHelper.dispatchNestedPreFling(velocityX, velocityY);
    }

    private boolean isAnimationRunning(Animation animation)
    {
        return animation != null && animation.hasStarted() && !animation.hasEnded();
    }

    /**
     * 移动Child控件。
     *
     * @param overScrollTop 移动到这个位置
     */
    private void moveSpinner(float overScrollTop)
    {
        float originalDragPercent = overScrollTop / mTotalDragDistance;
        float dragPercent = Math.min(1f, Math.abs(originalDragPercent));
        float adjustedPercent = (float) (dragPercent - .4) * 5 / 3;
        float extraOS = Math.abs(overScrollTop) - mTotalDragDistance;
        float slingshotDist = mTotalDragDistance;
        float tensionSlingshotPercent = Math.max(0, Math.min(extraOS, slingshotDist * 2) / slingshotDist);
        float tensionPercent = (float) ((tensionSlingshotPercent / 4) - Math.pow((tensionSlingshotPercent / 4), 2)) * 2f;
        float extraMove = (slingshotDist) * tensionPercent * 0.5f;

        int targetY = (int) ((slingshotDist * dragPercent) + extraMove);
        float rotation = (-0.25f + .4f * adjustedPercent + tensionPercent * 2) * .5f;
        mProgress.onPullCallback(rotation);
        setTargetOffsetTopAndBottom((int) (overScrollTop - mCurrentTargetOffsetTop), true);
    }

    private void finishSpinner(float overScrollTop)
    {
        if (overScrollTop > mTotalDragDistance)
        {
            setRefreshing(true, true);
        }
        else
        {
            // cancel refresh
            mRefreshing = false;
            animateOffsetToStartPosition(mCurrentTargetOffsetTop, mRefreshListener);
        }
    }


    private void animateOffsetToCorrectPosition(int from, AnimationListener listener)
    {
        mFrom = from;
        mAnimateToCorrectPosition.reset();
        mAnimateToCorrectPosition.setDuration(ANIMATE_TO_TRIGGER_DURATION);
        mAnimateToCorrectPosition.setInterpolator(mDecelerateInterpolator);
        mAnimateToCorrectPosition.setAnimationListener(listener);
        mTarget.clearAnimation();
        mTarget.startAnimation(mAnimateToCorrectPosition);
    }

    private void animateOffsetToStartPosition(int from, AnimationListener listener)
    {
        mFrom = from;
        mAnimateToStartPosition.reset();
        mAnimateToStartPosition.setDuration(ANIMATE_TO_START_DURATION);
        mAnimateToStartPosition.setInterpolator(mDecelerateInterpolator);
        mAnimateToStartPosition.setAnimationListener(listener);
        mTarget.clearAnimation();
        mTarget.startAnimation(mAnimateToStartPosition);
    }

    private void moveToStart(float interpolatedTime)
    {
        int targetTop = 0;
        targetTop = (mFrom - (int) (mFrom * interpolatedTime));
        int offset = targetTop - mTarget.getTop();
        setTargetOffsetTopAndBottom(offset, false);
    }

    /**
     * 移动目标的垂直位置
     *
     * @param offset         偏移量，大于0向下移动，小于0向上移动
     * @param requiresUpdate 是否更新UI
     */
    private void setTargetOffsetTopAndBottom(int offset, boolean requiresUpdate)
    {
        Log.d(TAG, "setTargetOffsetTopAndBottom:" + offset);
        mTarget.bringToFront();
        mTarget.offsetTopAndBottom(offset);
        mCurrentTargetOffsetTop = mTarget.getTop();
        if (requiresUpdate && android.os.Build.VERSION.SDK_INT < 11)
        {
            invalidate();
        }
    }

    /**
     * 刷新监听
     */
    public interface OnRefreshListener
    {
        public void onRefresh();
    }
}
